昨天我們探討了在建立可觀測性資料管道時的考量,以及為什麼最後選擇 AWS Kinesis Data Firehose + Lambda 作為我們的即時轉換方案。今天,從OTLP 批次匯出的資料結構開始,讓我們來深入探討 Firehose 細部的設定與設計考量。
首先,讓我們來看看 OTel Collector 實際匯出的資料長什麼樣子。以下是一個典型的 OTLP metrics 批次匯出範例:
{
"resourceMetrics": [
{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "api-server"}},
{"key": "host.name", "value": {"stringValue": "prod-host-1"}}
]
},
"scopeMetrics": [
{
"scope": {"name": "prometheus"},
"metrics": [
{
"name": "http_requests_total",
"sum": {
"dataPoints": [
{
"asInt": "1500",
"timeUnixNano": "1633024800000000000",
"attributes": [
{"key": "method", "value": {"stringValue": "GET"}},
{"key": "status", "value": {"stringValue": "200"}}
]
},
{
"asInt": "50",
"timeUnixNano": "1633024800000000000",
"attributes": [
{"key": "method", "value": {"stringValue": "POST"}},
{"key": "status", "value": {"stringValue": "500"}}
]
}
]
}
},
{
"name": "http_request_duration_seconds",
"histogram": {
"dataPoints": [
{
"count": "1500",
"sum": 450.5,
"bucketCounts": ["100", "800", "500", "100"],
"explicitBounds": [0.1, 0.5, 1.0, 2.0],
"timeUnixNano": "1633024800000000000"
}
]
}
}
]
}
]
}
]
}
從這個範例可以看到 OTLP 資料的幾個特性:
OTLP 採用高度巢狀的結構來組織資料:
resourceMetrics[]
:最外層陣列,包含多個 resourcescopeMetrics[]
:每個 resource 下可以有多個 scopemetrics[]
:每個 scope 下包含多個 metricdataPoints[]
:每個 metric 下包含多個資料點一個 OTLP 批次可能包含數百筆遙測資料點,而這些資料點全部被打包在一個 JSON 物件中。
service.name
、host.name
)method
、status
)而在查詢時,我們通常需要將這兩者合併成扁平化的欄位。
OTLP 支援多種 metric 類型,每種類型的資料結構不同:
這意味著我們需要針對不同類型的 metric 進行不同的轉換邏輯。而除了 Metrics 以外,Traces、Logs 也各有自己的轉換邏輯。
由於不同的 signal 所記錄的資料都各有不同,因此我們也會需要設計多張資料表來放置不同 signal 的紀錄。這時候,我們的 data pipeline 便要有能力去做到根據不同資料 routing 到正確的表格。而 Firehose 則能做到這一點。
儘管如此,Firehose 在處理資料時仍有一些限制。或許不能說是限制,而是它在處理、識別資料的特性。Firehose 處理資料的核心概念是 record:
在 Firehose 中,一個 record 代表一筆將要寫入目的地(如 S3 Table)的資料。當資料寫入 S3 Table 時,每個 record 會對應到 table 中的一筆資料(one row),並且會被分派一個 unique 的 record ID。
還記得在上一個段落提到,即便 OTLP 資料中有許多個資料點,只要它在同一批次的資料當中,它就會是同一筆 JSON 檔。
如果我們直接將整個 OTLP 批次當作一筆 record 送進 Firehose,最終在 S3 Table 中只會產生一筆資料,而不是我們預期的數百筆資料點。
假設我們直接將上面的 OTLP JSON 當作一筆 Firehose record:
API Gateway → Firehose (1 record) → S3 Table (1 row)
結果是:
我們需要的是:
API Gateway → Lambda (拆分) → Firehose (N records) → S3 Table (N rows)
這時候,我們就必須識別這包 JSON 檔當中共有幾筆資料,並把它拆成單獨的 request 丟給 Firehose。若我們使用 Firehose 內建的 Lambda,由於它是等到 Firehose 接收資料後,才去做後續的資料處理,所以在這個階段,並沒有辦法把 request 進行拆分。
Firehose 中內建的 Data Transform Lambda,可以直接在 Firehose 裡面進行設定
所以在這裡,我們所採取的架構就會是在 Firehose 前面再加一個 Lambda,去將一個 request 拆分成一筆一筆的紀錄。
今天我們深入探討了為什麼需要在 Firehose 前面加入一個 Lambda 進行資料拆分。核心原因在於:
明天,我們將展示如何實際配置 Firehose 並撰寫 Lambda 轉換函式。
OpenTelemetry Protocol Specification - Metrics
AWS Kinesis Data Firehose - Record Format Conversion
AWS Kinesis Data Firehose - Data Transformation
AWS re:Post - Uniqueness checks when streaming data via firehose to s3
OpenTelemetry Collector - Batch Processor